from xmlrpc.server import SimpleXMLRPCServer as SERVER
import xmlrpc.client as CLIENT
from CSYNAC.Memery import Memery
import CSYNAC.FileTree as Tree
import CSYNAC.Lib as L
from CSYNAC.Node import Node
import CSYNAC.Log as Log
import _thread as Thread

import os
from functools import partial



class FunsBase():
    def __init__(self,server:SERVER,memery:Memery):
        self.server:SERVER = server
        self.memery:Memery = memery

    # ---------------------------------------------------------------------------
    # ----------------------------   Server  ------------------------------------
    def shutdown(self):
        def _exit(server:SERVER):
            server.shutdown()
        Thread.start_new_thread(_exit, (self.server, ))
        return "Its ok to shutdown!"

    def echo(self, m):
        print(m)
        return m

    # ---------------------------------------------------------------------------
    # ----------------------------   os.file ------------------------------------
    def md5(self,file):
        if os.path.isfile(file):
            return L.md5_file(file)
        elif os.path.isdir(file):
            return L.md5_folder(file)
        else:
            raise Exception("Error! The file does not exist!")

    def folder_report(self, folder, c=False):
        return L.report_folder(folder,c)

    def delete(self,file):
        L.deletes([file,])

    def deletes(self,files):
        L.deletes(files)

    def delete_folder(self,folder):
        L.delete_folder(folder)

    def file_out(self, file:str):
        return CLIENT.Binary(L.file_out(file))

    def file_in(self,file:str,data):
        L.file_in(file,data.data)

    # def file_tree_build(self,folder,nackname):
    #     def tree_build(folder,nackname):
    #         self.memery.SynacFolders[nackname] = Tree.SFolder(folder,nackname)
    #         self.memery.SynacFolders[nackname].analyze()
    #     Thread.start_new_thread(tree_build,[folder,nackname])
    # @SynacTask
    def file_tree_build(self,folder,nackname):
        SFolder = Tree.SFolder(path=folder,nickname=nackname)
        self.memery.SynacFolders[nackname] = SFolder
        self.memery.SynacFolders[nackname].analyze()
        # self.memery.log_in('尝试着建立文件树,对象为文件夹{}'.format(folder),mtype='build file_tree')
    # def file_tree_build(self,nackname,folder):
    #     print("-----------------------------\n",folder)
    #     # self.memery.SynacFolders[nackname] =
    #     SFolder = Tree.SFolder(path=folder,nickname=nackname)
    #     self.memery.SynacFolders[nackname] = SFolder
    #     print(nackname,'\n-------------------------------')
    #     pass

    def file_tree_check(self, nackname)->bool:
        if nackname in [ folder for folder in self.memery.SynacFolders] \
                and self.memery.SynacFolders[nackname].flags['inited']:
            return True
        return False

    # @SynacTask
    def folder_md5s_calculate(self, nackname):
        SynacFolder:Tree.SFolder = self.memery.SynacFolders[nackname]
        SynacFolder.folders = {}
        SynacFolder.flags['md5_ready']=False
        def f(SynacFolder:Tree.SFolder,self:Tree.File):
            try:
                if type(self)==Tree.Folder:
                    md5 = self.md5()
                    rpath = self.rpath()
                    SynacFolder.folders[rpath] = md5
            except Exception:
                # print("----------------\nERROR! target:{}\n{}\n----------------".format(self.rpath(),Exception))
                while True:
                    pass
        SynacFolder.execute(partial(f,SynacFolder))
        SynacFolder.flags['md5_ready']=True

    def folder_md5s_fetch(self, nackname):
        SynacFolder:Tree.SFolder = self.memery.SynacFolders[nackname]
        # while SynacFolder.flags['md5_ready']==False:
        #     pass
        assert SynacFolder.flags['md5_ready']==True
        result = SynacFolder.folders.copy()
        SynacFolder.flags['md5_ready']=False
        SynacFolder.folders = {}
        return result

    # ----------------------------------------------------------------------------
    # ------------------------        Synac      ---------------------------------
    def push_folder(self,LocalDir,RemoteDir,ip,port, recursion=False,nackname=None):
        if recursion:
            self._push_mult_folder(nackname=nackname,LocalDir=LocalDir,RemoteDir=RemoteDir,ip=ip,port=port)
        else:
            self._push_single_folder(LocalDir=LocalDir,RemoteDir=RemoteDir,ip=ip,port=port)

    def pull_folder(self,LocalDir,RemoteDir,ip,port,recursion=False,nackname=None):
        if recursion:
            self._push_mult_folder(LocalDir=LocalDir,RemoteDir=RemoteDir,ip=ip,port=port,nackname=nackname)
        else:
            self._pull_single_folder(LocalDir=LocalDir,RemoteDir=RemoteDir,ip=ip,port=port)

    def _push_single_folder(self,LocalDir,RemoteDir,ip,port):
        def sname(apath):
            return os.path.split(apath)[-1]
        def lname(file):
            return os.path.join(LocalDir,file)
        def rname(file):
            return os.path.join(RemoteDir,file)
        server_stub:FunsBase = CLIENT.ServerProxy("http://{}:{}".format(ip, port))
        # raise Exception("此处首先要确保对面文件夹存在.!")
        # remote_report = server_stub.report(RemoteDir,True)
        remote_report = server_stub.folder_report(RemoteDir,True)
        local_report = L.report_folder(LocalDir)
        if local_report["md5"]==remote_report["md5"]:
            return None
        local_files = {sname(file):local_report['files'] for file in local_report["files"]}
        remote_files = {sname(file):remote_report['files'] for file in remote_report["files"]}
        for file in local_files:
            if file in remote_files:
                if not local_files[file] == remote_files[file]:
                    with open(lname(file),'rb') as f:
                        remote_name = rname(file)
                        data = f.read()
                        bdata = CLIENT.Binary(data)
                        server_stub.file_in(remote_name,bdata)
                del remote_files[file]
            else:
                with open(lname(file),'rb') as f:
                    server_stub.file_in(rname(file),CLIENT.Binary(f.read()))
        for file in remote_files:
            server_stub.delete(rname(file))

    def _pull_single_folder(self,LocalDir,RemoteDir,ip,port):
        def sname(apath):
            return os.path.split(apath)[-1]
        def rname(file):
            return os.path.join(RemoteDir,file)
        def lname(file):
            return os.path.join(LocalDir,file)
        server_stub:FunsBase = CLIENT.ServerProxy("http://{}:{}".format(ip,port))
        remote_report = server_stub.folder_report(RemoteDir)
        local_report = L.report_folder(LocalDir,True)
        if local_report["md5"] == remote_report["md5"]:
            return None
        local_files = {sname(file):local_report["files"][file] for file in local_report["files"]}
        remote_files = {sname(file):remote_report["files"][file] for file in remote_report["files"]}
        for file in remote_files:
            if file in local_files:
                if not local_files[file] == remote_files[file]:
                    data = server_stub.file_out(rname(file)).data
                    with open(lname(file),'wb') as f:
                        f.write(data)
                del local_files[file]
            else:
                data = server_stub.file_out(rname(file)).data
                with open(lname(file),'wb') as f:
                    f.write(data)
        for file in local_files:
            L.deletes([lname(file),])

    def _push_mult_folder(self,nackname,LocalDir,RemoteDir,ip,port):
        def sname(apath):
            return os.path.split(apath)[-1]
        def rname(file):
            return os.path.join(RemoteDir,file)
        def lname(file):
            return os.path.join(LocalDir,file)
        server_stub:FunsBase = CLIENT.ServerProxy("http://{}:{}".format(ip,port))
        server_stub.file_tree_build(RemoteDir,nackname)
        server_stub.folder_md5s_calculate(nackname)
        remote_report = server_stub.folder_md5s_fetch(nackname)
        self.file_tree_build(LocalDir,nackname)
        self.folder_md5s_calculate(nackname)
        # raise Exception('Error')
        local_report = self.folder_md5s_fetch(nackname)
        for folder in local_report:
            if folder in remote_report:
                if not local_report[folder]==remote_report[folder]:
                    self.push_folder(lname(folder),rname(folder),ip,port)
                del remote_report[folder]
            else:
                self.push_folder(lname(folder),rname(folder),ip,port)
        for folder in remote_report:
            with CLIENT.ServerProxy("http:{}:{}".format(ip,port)) as server_stub:
                server_stub.delete_folder(rname(folder))

    def _pull_mult_folder(self,nackname,LocalDir,RemoteDir,ip,port):
        def sname(apath):
            return os.path.split(apath)[-1]
        def rname(file):
            return os.path.join(RemoteDir,file)
        def lname(file):
            return os.path.join(LocalDir,file)
        server_stub:FunsBase = CLIENT.ServerProxy("http://{}:{}".format(ip,port))
        server_stub.file_tree_build(RemoteDir,nackname)
        server_stub.folder_md5s_calculate(nackname)
        remote_report = server_stub.folder_md5s_fetch(nackname)
        self.file_tree_build(LocalDir,nackname)
        self.folder_md5s_calculate(nackname)
        local_report = self.folder_md5s_fetch(nackname)
        for folder in remote_report:
            if folder in local_report:
                if not remote_report[folder]==local_report[folder]:
                    self.pull_folder(lname(folder),rname(folder),ip,port)
                del local_report[folder]
            else:
                self.pull_folder(lname(folder),rname(folder),ip,port)
        for folder in local_report:
            L.delete_folder(folder)



    # ----------------------------------------------------------------------------
    # -------------------------       Node       ---------------------------------
    def node_reg(self, nodename, hostname, host_ip, port):
        if not nodename in self.memery.node_keeper.all_node():
            self.memery.node_add(Node(nodename=nodename,hostname=hostname,host_ip=host_ip,port=port))
        else:
            print("节点已经存在")

    def node_self(self)->dict:
        return self.memery.node_self().__dict__

    def node_report(self):
        node_info = self.memery.node_report()
        result = {
            node.nodename:{
                "hostname":node.hostname,
                "nodename":node.nodename,
                "host_ip":node.host_ip,
                "port":node.port
            }
            for node in [node_info[nodename] for nodename in node_info]
        }
        return result

    def node_delete(self,nodename):
        self.memery.node_del(nodename)

    # def node_central_down(self):
    #     self.memery.node_del(self.)

    # ----------------------------------------------------------------------------
    # -------------------------       Log        ---------------------------------
    def add_log(self,message:str,eventID:str,mtype:str='normal',node:Node=None):
        if not node:
            node = self.memery.node_keeper.all_node()[self.memery.nodename]
        self.memery.log_in(message,node=node,eventID=eventID,mtype=mtype)

    def fetch_all_log(self):
        return self.memery.log_all()

    def fetch_by_node(self):
        raise Exception("Error")
        pass

    def fetch_by_taskID(self):
        raise Exception('ERROR')
        pass

    def fetch_by_time(self):
        raise Exception("Error")
        pass

